/*
	Copyright 2009 Tomer Gabel <tomer@tomergabel.com>

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

		http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.


	ant-intellij-tasks project (http://code.google.com/p/ant-intellij-tasks/)

	$Id: Lazy.java 106 2009-09-30 02:07:29Z tomergabel $
*/

package com.tomergabel.util;

import java.util.concurrent.Callable;

/**
 * Utility class that provides a lazy initialization object wrapper.
 * <p/>
 * To use this wrapper simply implement {@link java.util.concurrent.Callable#call()} and return the appropriate value.
 * Exceptions are propagated by {@link #get()} as {@link com.tomergabel.util.LazyInitializationException}s.
 * <p/>
 * <strong>Note: This wrapper is <em>not</em> thread-safe!</strong>
 *
 * @param <T> The type wrapped by the lazy initializer.
 */
public abstract class Lazy<T> implements Callable<T> {
    /**
     * The actual value.
     */
    private T value = null;

    /**
     * Protected c'tor (to prevent direct initialization.
     */
    protected Lazy() {
    }

    /**
     * A private c'tor used to pre-cache the value. This is used by the convenience method {@link #from(Object)} to
     * create a "const" lazy initializer.
     *
     * @param value The actual value.
     */
    private Lazy( final T value ) {
        this.value = value;
    }

    /**
     * Retreives the lazy-loaded value, initializing it on the fly if necessary.
     *
     * @return The lazy-loaded value.
     * @throws LazyInitializationException An error has occurred while initializing the lazy-loaded value. Please see
     *                                     the exception {@link Exception#getCause() cause} for detials.
     */
    public T get() throws LazyInitializationException {
        try {
            return this.value == null ? ( this.value = this.call() ) : this.value;
        } catch ( Exception e ) {
            throw new LazyInitializationException( e );
        }
    }

    /**
     * Wraps a value with a pre-cached value. This is a convenience method intended to provide an easy way to specify
     * constant values to a lazy-loaded placeholder if necessary.
     *
     * @param value The actual value.
     * @param <T>   The type wrapped by the lazy initializer.
     * @return A {@link Lazy} instance for the specified value.
     */
    public static <T> Lazy<T> from( final T value ) {
        return new Lazy<T>( value ) {
            @Override
            public T call() throws Exception {
                // Safety net, should never happen
                throw new IllegalStateException( "Lazy initializer should be not called on lazy constants." );
            }
        };
    }

    /**
     * Wraps the specified callable with a {@link Lazy} instance. This is an alternative to extending this class.
     *
     * @param initializer The initializer. Calling {@link #from} with {@literal null} for an argument will resolve here,
     *                    so specifying a {@literal null} initializer is the same as specifying a callable which returns
     *                    {@literal null}.
     * @return A {@link Lazy} instance for the specified initializer.
     */
    @SuppressWarnings( { "unchecked" } )
    public static <T> Lazy<T> from( final Callable<T> initializer ) {
        return new Lazy<T>() {
            @Override
            public T call() throws Exception {
                // Lazy.from( null ) will resolve here, so (as a convenience) we support
                // return of null values
                return initializer == null ? null : initializer.call();
            }
        };
    }
}
